# 接下来的任务：做删除连接线的效果，接下来再弄删除block的效果。删除block的时候需要注意
# 顺序，先删除block的所有line，然后将端口状态复原，接下来才能删除block。
# 再接下来需要做属性编辑框这个控件了。属性编辑框是一种很重要的控件！
# 做好属性编辑框之后就可以做图形元素改变的效果了。注意图形元素改变应该如何在界面上体现，比如
# 端口数目的增减，或者是文字内容的改变，以及其附属关系。
import tkinter as tk
print(tk.CURRENT)
import traceback
import math
import time
import numpy as np
from node import Node,nodeModel
from settingsframe import SettingsFrame
class Curve():
    def __init__(self,canvas:tk.Canvas):
        
        self.canvas = canvas
        self.item_ids=  []
        self.phase=  0
        self.draw()
        self.canvas .after(100,self.refresh)
        
    def draw(self):
        for x in np.linspace(0,6.29,100):
            x0,y0 = x*100 , math.sin(x)*100+200
            w, h = 5, 5
            x1,x2 = x0-w/2, x0+w/2
            y1,y2 = y0-h/2 , y0+h/2
            item_id = self.canvas. create_oval(x1,y1,x2,y2,fill='black')
            self.item_ids.append(item_id)

    def refresh(self):
        t0 = time.time()
        for i,x in enumerate(np.linspace(0,6.29,100)):
            x0,y0 = x*100 , math.sin(x+self.phase)*100+200
            self.canvas.move_to(self.item_ids[i],x0,y0)
            
        self.phase+=0.1
        self.canvas.after(100,self.refresh)
        t1=time.time()
        print(t1-t0)

class Line():
    def __init__(self,diagram_model, startPort,endPort):
        self.diagram_model = diagram_model
        self.startPort = startPort
        self.endPort = endPort
        self.startPort.line = self
        self.endPort.line = self
        self.lineId = -1
        
    def update(self):
        s_x,s_y = self.diagram_model.coords(self.startPort.shape_id)[:2]# 开始坐标
        e_x,e_y = self.diagram_model.coords(self.endPort.shape_id)[:2]# 结束坐标
        if self.lineId == -1:
            self.lineId = self.diagram_model.create_conn_line((s_x+5,s_y+5),(e_x+5,e_y+5))
        else:
            self.diagram_model.set_conn_line_pos(self.lineId,startPos = (s_x+5,s_y+5),endPos = (e_x+5,e_y+5))
    
    def on_delete(self):
        self.startPort.line = None
        self.endPort.line = None
        self.startPort.node.refresh_ports()
        self.endPort.node.refresh_ports()
        
    def get_property_dic(self):
        return {'startNode':self.startPort.node.name, 'endNode':self.endPort.node.name,
                'startPort':self.startPort.name, 'endPort':self.endPort.name}
        
        
        
        

class Block():
    '''
    "块"
    其中的所有内容都会一起移动。
    '''
    def __init__(self, diagram_model):
        self.item_ids = []
        self.diagram_model = diagram_model
        self.set_item_mapping()
        self.node = None
        self.pos = (0,0)
    
    def refresh_look(self):
        '''
        当self.node的属性变化后，Block的显示不会自动更新。
        此函数的目的是按照node的属性，逐个更新显示的信息。
        '''
        text = '%d:%s\n%s'%(self.node.id, self.node.name, self.node.func_name)
        text_id = self.node.text_id
        self.diagram_model.canvas.itemconfig(text_id,text = text)
        for input_port in self.node.input_ports:
            text_id = input_port.text_id
            self.diagram_model.canvas.itemconfig(text_id,text = input_port.name)
        for output_port in self.node.output_ports:
            print(output_port,dir(output_port))
            text_id = output_port.text_id
            self.diagram_model.canvas.itemconfig(text_id,text = output_port.name)
        
    def move_to(self, x:float , y:float):
        x0,y0 = self.diagram_model.coords(self.item_ids[0])[:2]
        x_rela, y_rela = x-x0,y-y0
        for id in self.item_ids:
            self.diagram_model.move(id, dpos =(x_rela,y_rela))
           
    def set_item_mapping(self):
        for id in self.item_ids:
            self.diagram_model.block_shapes_dic[id]=self
    
    def add_item(self,item_id:int):
        self.item_ids.append(item_id)
    
    def del_item(self, item_id:int):
        try:
            self.item_ids.pop(self.item_ids.index(item_id))
        except:
            traceback.print_exc()

    def get_property_dic(self):
        d1 = {'pos':self.diagram_model.coords(self.item_ids[0])}
        d2 = self.node.get_property_dic()
        d1.update(d2)
        return   d1
            
class MyCanvas(tk.Canvas):
    def __init__(self,parent):
        super().__init__(parent)
        self.parent = parent
        self.itemToMove = None
        self.relativePos = ()
        self.lastPos = ()
        self.bind('<ButtonPress-2>',self.on_mouse_central_click)
        self.bind('<ButtonPress-1>',self.on_mouse_down)
        self.bind('<B1-Motion>',self.on_mouse_drag)
        self.bind('<Double-Button-1>',self.on_mouse_double_click)
        self.bind('<Control-S>',self.save_info)    
        self.diagram_model = DiagramModel(self)
        
        self.drawingLine = False # 判断是否在绘制线段。
        self.portSelected = None
    
    def on_mouse_double_click(self,event):
        a = self.find_withtag(tk.CURRENT)  
        if a:
            block = self.diagram_model.block_shapes_dic[a[0]]
            print(block)
            self.parent.settings_frame.set_node_info(block.node)
    def on_mouse_central_click(self,event):# 当中键点击时
        a = self.find_withtag(tk.CURRENT)
        if a:
            self.diagram_model.delete_line(a[0])
            self.delete(a[0])
            
    def on_mouse_down(self,event):
        '''
        鼠标按下的时候如果点到了端口，那么就进入画线模式。
        但是，只有从输出端开始到输入端结束才能顺利画线！
        '''
        a = self.find_withtag(tk.CURRENT)
        if a:
            port = self.diagram_model.port_shape_dic.get(a[0])
            line = self.diagram_model.line_shape_dic.get(a[0])
            if port:
                if self.drawingLine == False:
                    if port.dir == 'output':
                        self.portSelected = port
                        self.drawingLine = True
                        return
                else:
                    if port.dir == 'input':
                        makeLine(self,self.portSelected,port)
                        self.drawingLine  = False
                        return
                print('Warning: A line can only start from an output port and end in an input port.')
            else:
                self.record_relative_pos(event)
        
    def record_relative_pos(self,event):
        self.relativePos=()
        a = self.find_withtag(tk.CURRENT)
        if len(a)>=1:
             coor = self.coords(a[0])
             x,y =  event.x-coor[0],event.y-coor[1]
             self.relativePos = (x,y)
             self.itemToMove  = a[0]
        else:
            self.itemToMove = None
        
    def move_to(self,item_id,x,y):
        pos = self.coords(item_id)
        self.move(item_id, x-pos[0], y-pos[1])
        
    def on_mouse_drag(self,event):
        if not self.itemToMove: # 如果没有要移动的对象，就直接return,防止出现奇奇怪怪的错误。 
            return 
        
        a = self.find_withtag(tk.CURRENT)
        
        if len(a)>=1:
            self.diagram_model.block_shapes_dic[a[0]].move_to(event.x-self.relativePos[0], event.y-self.relativePos[1])
            for k in self.diagram_model.line_shape_dic:
                self.diagram_model.line_shape_dic[k].update()
    
    def save_info(self):
        line_info_list = []
        block_info_list = []
        for line_name in self.diagram_model.line_shape_dic.keys():
            line_info_list.append(self.diagram_model.line_shape_dic[line_name].get_property_dic())
        for block in list(set(self.diagram_model.block_shapes_dic.values())):
            block_info_list.append(block.get_property_dic())
        import json
        s= json.dumps([line_info_list,block_info_list])
        print(s)
        with open ('/home/hzy/Desktop/test.tkfc','w') as f:
            f.write(s)
    
    def load_nodes(self):
        import json
        with open('/home/hzy/Desktop/test.tkfc' ,'r') as f:
            s = f.read()
        lines_info, blocks_info = json.loads(s)
        for b_info in blocks_info:
            node = Node(b_info['name'], node_id = b_info['id'], func_name  = b_info['func'],
                        inputVarNames = b_info['input'], outputVarNames = b_info['output'])
            x ,y ,x2, y2 = b_info['pos']
            w, h = x2-x, y2-y
            makeBlock(self, node, posx=x,posy=y, width=w,height=h)
        for l_info in lines_info:
            line = makeLineByPortName(self,startNode = l_info['startNode'], endNode = l_info['endNode'],
                                      endPort = l_info['endPort'], startPort = l_info['startPort'] )
            

class DiagramModel():
    def __init__(self,canvas):
        self.port_shape_dic = {}# 包含port的全部元素
        self.canvas = canvas   
        self.block_shapes_dic = {}# 从canvas上id到block的映射。包含block的全部图形元素。当拖动等事件发生时，修改这个字典中的对象。
        self.line_shape_dic = {}     # 从id到直线段的字典映射
        
    def find_node_by_name(self,name=''):
        for block in list(set(self.block_shapes_dic.values())):
            if block.node.name == name:
                return block.node
        raise Exception('node name %s not found!'%name)
    
    def delete_line(self, line_id):
        if line_id in self.line_shape_dic.keys():
            line = self.line_shape_dic.pop(line_id)
            line.on_delete()
    
    def create_conn_line(self,startPos=(0,0), endPos = (0,0)):
        line_id = self.canvas.create_line(startPos[0], startPos[1], endPos[0], endPos[1], activefill = 'red')
        return line_id
        
    def set_conn_line_pos(self,line_id:int, startPos = (0,0), endPos = (0,0)):
         self.canvas.coords(line_id, startPos[0], startPos[1], endPos[0], endPos[1])
        
    def coords(self,item_id:int):
        return self.canvas.coords(item_id)
    
    def move(self,item_id, dpos = (0,0)):
        self.canvas.move(item_id, dpos[0], dpos[1])
        
    def move_to(self,item_id,pos = (0,0)):  
        self.canvas.move_to(item_id, pos[0], pos[1])   
        
def makeLineByPortName(canvas,startNode:str, endNode:str, startPort:str, endPort:str):
    startPort= canvas.diagram_model.find_node_by_name(startNode).find_port_by_name(startPort,'output')
    endPort = canvas.diagram_model.find_node_by_name(endNode).find_port_by_name(endPort,'input')
    l = makeLine(canvas,startPort,endPort)# Line(canvas=canvas, startPort=startPort,endPort=endPort)
    return l

def makeLine(canvas,startPort,endPort):
    endPort.name = startPort.name
    l = Line(diagram_model=canvas.diagram_model, startPort=startPort,endPort=endPort)
    l.update()
    canvas.diagram_model.line_shape_dic[l.lineId] = l
    endPort.node.refresh_ports()
    startPort.node.refresh_ports()
    return l

def makeBlock(canvas,node,posx=0,posy=100,width=80,height=120):
    b = Block(canvas.diagram_model)
    b.node = node
    node.block = b# 相互引用吧。
    b.item_ids.append(canvas.create_rectangle(posx,posy,posx+width,posy+height, fill='white'))
    r=5
    for i,p in enumerate(node.input_ports):
        x = posx
        y = posy + height/( len(node.input_ports)+1)*(i+1)
        
        port_shape = canvas.create_oval(x-r,y-r,x+r,y+r, fill='grey',activefill='yellow')
        b.item_ids.append(port_shape)
        p.shape_id = port_shape 
        canvas.diagram_model.port_shape_dic[port_shape] = p
        
        text_shape =  canvas.create_text((x-r ,y-2*r),text=p.name,  fill = 'red',        #填充颜色
                   anchor = tk.W,          #对齐方式
                   justify = tk.LEFT
                   )
        b.item_ids.append(text_shape)
        p.text_id = text_shape
        
    text_shape =  canvas.create_text((posx+width/2-2*r ,posy+height*1/4),
                    text='%d:%s\n%s'%(node.id,node.name,node.func_name), 
                    fill = 'black',        #填充颜色
                   anchor = tk.W,          #对齐方式
                   justify = tk.LEFT
                   )
    b.item_ids.append(text_shape)
    node.text_id = text_shape# 包含node.id和node.name两个的显示。
    
    for i,p in enumerate(node.output_ports):
        x = posx + width
        y = posy+ height/(len(node.output_ports)+1)*(i+1)
        port_shape = canvas. create_oval(x-r,y-r,x+r,y+r, fill='grey',activefill='yellow')
        b.item_ids.append(port_shape)
        p.shape_id = port_shape
        
        text_shape =  canvas.create_text((x-r ,y-2*r),text=p.name,  fill = 'red',        #填充颜色
                   anchor = tk.W,          #对齐方式
                   justify = tk.LEFT
                   )
        p.text_id = text_shape
        b.item_ids.append(text_shape)
        canvas.diagram_model.port_shape_dic[port_shape] = p
    b.set_item_mapping()
    return b

def gen_code():
     
    nodeModel.sort()
    nodeModel.gen_code()
    nodeModel.gen_controller()

def save():
    master = tk.Tk()
    
    w = MyCanvas(master)
    w.pack(fill=tk.BOTH,expand=1)
    
    b = Node('b',8 ,inputVarNames=[],outputVarNames = ['output'])
    block = makeBlock(w,b)
    b2 = Node('c',3 ,inputVarNames=['input3','input32'],outputVarNames = [])
    block2 = makeBlock(w,b2,posx=130,posy=130)
    
    b3 = Node('d',5 ,inputVarNames=['inp'],outputVarNames = ['output33'])
    block3 = makeBlock(w,b3,posx=150,posy=150)
    
    s=block2.get_property_dic()
    l = makeLineByPortName(w,startNode = 'b', endNode = 'c', startPort = 'output',endPort = 'input3')
##    l = makeLineByPortName(w,startNode = 'b', endNode = 'c', startPort = 'output',endPort = 'input32')
    b = tk.Button(text='save',command = w.save_info)
    b.pack()
    b1 = tk.Button(text='gencode',command = gen_code())
    b1.pack()
    sf = SettingsFrame(master)
    sf.set_node_info(b3)
    sf.pack()
    tk.mainloop() 

def load():
    master = tk.Tk()

    w = MyCanvas(master)
    w.pack(fill=tk.BOTH,expand=1)
    w.load_nodes()
    b = tk.Button(text='save',command = w.save_info)
    b2  = tk.Button(text = 'gencode', command = gen_code)

    sf = SettingsFrame(master)
    
    sf.pack()
    master.settings_frame = sf
    b.pack()
    b2.pack()

    tk.mainloop() 
if __name__ == '__main__':
#    save()
    load()
